
// 日志落地
// 落地方式：标准输出，指定文件，滚动大小

#ifndef __SINK__
#define __SINK__

#include "util.hpp"
#include <memory>
#include <fstream>
#include <sstream>
#include <cassert>

namespace violet_log
{
    // 抽象出基类
    class log_sink
    {
    public:
        using ptr = std::shared_ptr<log_sink>;

        log_sink()
        {}

        virtual ~log_sink()
        {}

        virtual void log(const char *data, const size_t len) = 0;
    };

    // 落地方式：标准输出
    class stdout_sink : public log_sink
    {
    public:
        stdout_sink() = default;

        void log(const char *data, const size_t len)
        {
            std::cout.write(data, len);
        }
    };

    // 落地方式：指定文件
    class file_sink : public log_sink
    {
    public:
        file_sink(const std::string& pathname)
        :_pathname(pathname)
        {
            // 创建目录
            tools::file::creat_directory(tools::file::get_path(pathname));

            // 打开文件
            _ofs.open(pathname, std::ios::binary | std::ios::app);
            if(_ofs.is_open() == false)
            {
                std::cout << "指定文件，文件打开失败" << std::endl;
                exit(-1);
            }
        }

        void log(const char *data, const size_t len)
        {
            _ofs.write(data, len);
            if(_ofs.good() == false)
            {
                std::cout << "指定文件，日志写入失败" << std::endl;
                exit(-1);
            }
        }

    private:
        std::string _pathname;
        std::ofstream _ofs;
    };

    // 落地方式：滚动大小
    class rollsize_sink : public log_sink
    {
    public:
        // 传入基础文件名，一个文件的大小上限
        rollsize_sink(const std::string& base_name, const size_t max_size)
        :_base_name(base_name)
        ,_max_fsize(max_size)
        ,_cur_fsize(0)
        ,_name_count(1)
        {
            // 创建目录
            tools::file::creat_directory(tools::file::get_path(base_name));
        }

        void log(const char *data, const size_t len)
        {
            // 初始化
            file_init();
            _ofs.write(data, len);
            if(_ofs.good() == false)
            {
                std::cout << "滚动大小，日志写入失败" << std::endl;
                exit(-1);
            }
            _cur_fsize += len;
        }
    
    private:
        void file_init()
        {
            if(_ofs.is_open() == false || _cur_fsize > _max_fsize)
            //if(_cur_fsize > _max_fsize)
            {
                _ofs.close();
                std::string file_name = creat_file_name();

                // 打开文件
                _ofs.open(file_name, std::ios::binary | std::ios::app);
                if(_ofs.is_open() == false)
                {
                    std::cout << "滚动大小，文件打开失败" << std::endl;
                    exit(-1);
                }
                _cur_fsize = 0;
            }
        }

        std::string creat_file_name()
        {
            // 将时间作为文件的拓展名
            time_t t = tools::get_time::_get_time();
            struct tm res;
            localtime_r(&t, &res);
            std::stringstream file_name;
            file_name << _base_name;
            file_name << res.tm_year + 1900 << '-' << res.tm_mon + 1 << '-' << res.tm_mday;
            file_name << ' ';
            file_name << res.tm_hour << ':' << res.tm_min << ':' << res.tm_sec;
            file_name << ' ' << _name_count++;
            file_name << ".log";
            return file_name.str();
        }

    private:
        std::string _base_name;//基础文件名
        std::ofstream _ofs;
        size_t _max_fsize;
        size_t _cur_fsize;
        size_t _name_count;
    };

    // 工厂模式是一种创建型设计模式，它提供了一种创建对象的最佳方式
    // 在工厂模式中，我们创建对象时不会对上层暴露创建逻辑，而是通过使用一个共同的结构来指向新创建的对象
    // 以此来实现创建与使用的分离
    // 简单工厂模式的实现是由一个工厂对象通过类型决定创建出来指定产品类的实例
    // 假设有个工厂可以生产水果，当用户需要产品时明确告知工厂生产哪类水果
    // 工厂得知水果种类后，去内部添加新产品的生产方式
    // 简单工厂模式
    class sink_factory
    {
    public:
        // 方便拓展，使用可变参数
        template<class SinkType, class ...Args>
        static log_sink::ptr creat(Args &&...args)
        {
            // 完美转发forward
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
};

#endif